package com.android.yummycookies.asyncTask;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.util.SparseArray;

import com.android.yummycookies.BuildConfig;
import com.android.yummycookies.R;
import com.android.yummycookies.model.commonModel.ObserversPool;
import com.android.yummycookies.storage.BitmapStorage;
import com.android.yummycookies.types.CookieType;
import com.android.yummycookies.util.Constant;
import com.android.yummycookies.util.Event;
import com.android.yummycookies.util.Logger;

public class LoadCookieTypesThread extends Thread {

	public static final String TAG = LoadCookieTypesThread.class
			.getSimpleName();

	/**
	 * Constante indiquant le debut du chargement des types de cookies
	 */
	public static final int START_LOADING_COOKIE_TYPES = Event.START_LOADING_COOKIE_TYPES;
	/**
	 * Constante indiquant qu'un type de cookie a ete charge
	 */
	public static final int COOKIE_TYPE_LOADED = Event.COOKIE_TYPE_LOADED;
	/**
	 * Constante indiquant qu'un type de cookie n'a pas pu etre charge
	 */
	public static final int COOKIE_TYPE_NOT_LOADED = Event.COOKIE_TYPE_NOT_LOADED;
	/**
	 * Constante indiquant la fin du chargement des types de cookies
	 */
	public static final int END_LOADING_COOKIE_TYPES = Event.END_LOADING_COOKIE_TYPES;

	/*****************************************************************
	 * Contexte de persistance
	 *****************************************************************/

	/** Context applicatif */
	private final Context context;
	/** BitmapStorage applicatif */
	private final BitmapStorage bitmapStorage;

	/*****************************************************************
	 * Configuration du redimensionnement
	 *****************************************************************/

	/** valeur de la hauteur du redimensionnement */
	private final int heightScale;
	/** valeur de la largeur du redimensionnement */
	private final int widthScale;

	/*****************************************************************
	 * Collections representant l'avancement du chargement des types de cookies
	 *****************************************************************/

	/** Ensemble des identifiants des bitmap en cours de chargement */
	private final Set<Integer> loadingBitmaps;
	/** Ensemble des identifiants des bitmap en cours de redimensionnement */
	private final Set<Integer> scalingBitmaps;

	/** Ensemble de type de cookies a charger */
	private final List<CookieType> cookieTypesToLoad;
	/** Map contenant les types de cookies qui ont ete charges */
	private final Set<CookieType> loadedCookieTypes = new HashSet<CookieType>();

	/******************************************************************
	 * Gestion des messages
	 ******************************************************************/
	private final ObserversPool observablePool = new ObserversPool();

	public LoadCookieTypesThread(Context context, BitmapStorage bitmapStorage,
			Handler observer, Set<Integer> loadingBitmaps,
			Set<Integer> scalingBitmaps, int height, int width,
			List<CookieType> cookieTypesToLoad) {

		this.context = context;
		this.bitmapStorage = bitmapStorage;
		this.observablePool.addObserver(observer);
		this.loadingBitmaps = loadingBitmaps;
		this.scalingBitmaps = scalingBitmaps;
		this.cookieTypesToLoad = cookieTypesToLoad;
		this.heightScale = height;
		this.widthScale = width;

		Logger.logDebug(TAG, TAG + " created");
	}

	public LoadCookieTypesThread(Context context, BitmapStorage bitmapStorage,
			Set<WeakReference<Handler>> observers, Set<Integer> loadingBitmaps,
			Set<Integer> scalingBitmaps, int height, int width,
			List<CookieType> cookieTypesToLoad) {

		this.context = context;
		this.bitmapStorage = bitmapStorage;
		this.observablePool.addAllObservers(observers);
		this.loadingBitmaps = loadingBitmaps;
		this.scalingBitmaps = scalingBitmaps;
		this.cookieTypesToLoad = cookieTypesToLoad;
		this.heightScale = height;
		this.widthScale = width;

		Logger.logDebug(TAG, TAG + " created");
	}

	@Override
	public void run() {
		sendStartMessage();

		for (CookieType ct : cookieTypesToLoad) {

			if (loadBitmaps(ct.getBitmapsId())) {
				if (scaleBitmaps(ct.getScalableBitmapsId())) {
					hydrateCookieTypeBitmaps(ct);
					loadedCookieTypes.add(ct);
					sendLoadedMessage(COOKIE_TYPE_LOADED, ct);
				} else {
					sendLoadedMessage(COOKIE_TYPE_NOT_LOADED, ct);
				}
			} else {
				sendLoadedMessage(COOKIE_TYPE_NOT_LOADED, ct);
			}
		}

		sendEndMessage();
	}

	private boolean loadBitmaps(Set<Integer> drawablesToLoad) {
		SparseArray<Bitmap> loadedBitmaps = new SparseArray<Bitmap>();
		Set<Integer> alreadyLoadedBitmaps = new HashSet<Integer>();

		// charge tous les bitmap
		for (Integer drawableId : drawablesToLoad) {
			// chargement du bitmap si il n'est pas dans le
			// bitmapStorage
			if (!bitmapStorage.isStored(drawableId)) {
				// ajoute l'id du bitmap dans la liste des bitmap
				// en cours de chargement
				boolean isLoading = false;
				synchronized (loadingBitmaps) {
					isLoading = loadingBitmaps.contains(drawableId);
					if (!isLoading) {
						loadingBitmaps.add(drawableId);
					}
				}

				// si le bitmap n'est pas en cours de chargement
				if (!isLoading) {

					Bitmap b = BitmapFactory.decodeResource(
							context.getResources(), drawableId);

					// si le bitmap a ete effectivement charge
					if (b != null) {
						// ajout du bitmap charge dans le bitmapStorage
						bitmapStorage.put(drawableId, b);
						// ajout du bitmap dans l'ensemble des bitmap charges
						loadedBitmaps.put(drawableId, b);
						Logger.logDebug(TAG, "Bitmap loaded: succeded",
								drawableId);
					} else {
						Logger.logDebug(TAG, "Bitmap loaded: failed",
								drawableId);
						Log.e(TAG, "id =>" + drawableId);
						Log.e(TAG, "bgId =>" + R.drawable.checkeroard_cookie);
						Log.e(TAG, "id donut =>" + R.drawable.donut);
						Log.e(TAG, "id checkboard =>"
								+ R.drawable.checkeroard_cookie);
					}
					// suppression du bitmap des bitmap en cours de chargement
					loadingBitmaps.remove(drawableId);

				} else {
					alreadyLoadedBitmaps.add(drawableId);
					Logger.logDebug(TAG, "Bitmap already loading", drawableId);
				}

			} else {
				alreadyLoadedBitmaps.add(drawableId);
				Logger.logDebug(TAG, "Bitmap already loaded", drawableId);
			}
		}

		return drawablesToLoad.size() == (loadedBitmaps.size() + alreadyLoadedBitmaps
				.size());
	}

	private boolean scaleBitmaps(Set<Integer> bitmapsIdsToScale) {
		SparseArray<Bitmap> scaledBitmaps = new SparseArray<Bitmap>();
		Set<Integer> alreadyScaledBitmaps = new HashSet<Integer>();

		// redimensionne tous les bitmap
		for (Integer bitmapId : bitmapsIdsToScale) {
			// redimensionnement du bitmap si il n'est pas dans le
			// bitmapStorage
			if (!bitmapStorage.isScaledBitmapStored(bitmapId)) {
				// ajoute l'id du bitmap dans la liste des bitmap
				// en cours de redimensionnement
				boolean isScaling = false;
				synchronized (scalingBitmaps) {
					isScaling = scalingBitmaps.contains(bitmapId);
					if (!isScaling) {
						scalingBitmaps.add(bitmapId);
					}
				}

				// si le bitmap n'est pas en cours de redimensionnement
				if (!isScaling) {
					// on recupere le bitmap a redimensionner
					Bitmap b = bitmapStorage.get(bitmapId);
					if (b != null) {
						Bitmap bitmapScaled = Bitmap.createScaledBitmap(b,
								widthScale, heightScale, false);
						// suppression du bitmap des bitmap en cours de
						// redimensionnement
						scaledBitmaps.put(bitmapId, bitmapScaled);

						if (bitmapScaled != null) {
							// ajout du bitmap redimensionne dans le
							// bitmapStorage
							bitmapStorage.putScaledBitmap(bitmapId, b);
							Logger.logDebug(TAG,
									"Send scale message: succeded", bitmapId);
						} else {
							Logger.logDebug(TAG, "Send scale message: failed",
									bitmapId);
						}
						scalingBitmaps.remove(bitmapId);

					} else {
						Logger.logDebug(TAG, "Bitmap not in storage", bitmapId);
					}
				} else {
					alreadyScaledBitmaps.add(bitmapId);
					Logger.logDebug(TAG, "Bitmap already scaling", bitmapId);
				}

			} else {
				alreadyScaledBitmaps.add(bitmapId);
				Logger.logDebug(TAG, "Bitmap already scaled", bitmapId);
			}
		}

		return bitmapsIdsToScale.size() == (scaledBitmaps.size() + alreadyScaledBitmaps
				.size());
	}

	private void hydrateCookieTypeBitmaps(CookieType cookieType) {
		cookieType.setBitmap(bitmapStorage.getScaledBitmap(cookieType
				.getBitmapId()));
		cookieType.setDestructionBitmap(bitmapStorage
				.getScaledBitmap(cookieType.getDestructionBitmapId()));
	}

	/***************************************************************************
	 * Gestion des messages
	 ***************************************************************************/

	/**
	 * Envoie un message de debut de chargement de types de cookies obj:
	 * l'ensemble des types de cookies a charger
	 */
	private void sendStartMessage() {
		Logger.logDebug(TAG, "Send start message", cookieTypesToLoad);
		for (WeakReference<Handler> wh : observablePool.getObservers()) {
			Handler h = wh.get();
			if (h != null) {
				Message m = h.obtainMessage();
				m.what = START_LOADING_COOKIE_TYPES;
				m.obj = cookieTypesToLoad;
				h.sendMessage(m);
			}

		}
	}

	/**
	 * Envoie un message de chargement types de cookies arg1: index ordinal d'un
	 * type de cookie
	 * 
	 * @param event
	 *            l'etat de reussite du chargement du type de cookie
	 * @param cookieType
	 *            le type de cookie charge
	 */
	private void sendLoadedMessage(int event, CookieType cookieType) {
		if (BuildConfig.DEBUG) {
			String state = "failed";
			switch (event) {
			case COOKIE_TYPE_LOADED:
				state = "succeded";
				break;
			default:
				break;
			}
			Logger.logDebug(TAG, "Send load message: " + state,
					cookieType.ordinal());
		}
		for (WeakReference<Handler> wh : observablePool.getObservers()) {
			Handler h = wh.get();
			if (h != null) {
				Message m = h.obtainMessage();
				m.what = event;
				m.arg1 = cookieType.ordinal();
				h.sendMessage(m);
			}

		}
	}

	/**
	 * Envoie un message de fin de chargement de types de cookies obj: un
	 * ensemble de types de cookies
	 */
	private void sendEndMessage() {
		if (BuildConfig.DEBUG) {
			Set<Integer> ids = new HashSet<Integer>();
			for (CookieType ct : loadedCookieTypes) {
				ids.add(ct.ordinal());
			}
			Logger.logDebug(TAG, "Send end message", ids);
		}
		for (WeakReference<Handler> wh : observablePool.getObservers()) {
			Handler h = wh.get();
			if (h != null) {
				Message m = new Message();
				m.what = END_LOADING_COOKIE_TYPES;
				m.obj = loadedCookieTypes;
				h.sendMessage(m);
			}
		}
	}
}
